#include "apue.h"
#include <sys/wait.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define     page_map_file       "/proc/self/pagemap"
#define     PFN_MASK            ((((uint64_t)1)<<55)-1)
#define     PFN_PRESENT_FLAG    (((uint64_t)1)<<63)

int mem_addr_vir2phy(unsigned long vir, unsigned long *phy);

int		globvar = 6;		/* external variable in initialized data */
char	buf[] = "a write to stdout\n";
int		var;

int main(void)
{
	// 将调用进程的全部虚拟地址空间加锁。防止出现内存交换，将该进程的地址空间交换到外存上
	if( mlockall(MCL_CURRENT) == -1){
		err_sys("write error");
	}

	// static int		var;		/* automatic variable on the stack */
	pid_t	pid;

	var = 88;
	if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
		err_sys("write error");
	printf("before fork\n");	/* we don't flush stdout */

	// 打印变量地址，确定变量的物理内存是否被复制
	printf("globvar addr:0x%p\n",&globvar);
	printf("var 	addr:0x%p\n",&var);
	printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,var);

	// 执行系统调用，陷入内核空间执行，创建新的进程，创建成功后，接口会返回两次，如下代码根据物理地址空间验证fork接口的copy-on-write
	if ((pid = fork()) < 0) {
		err_sys("fork error");
	}
	// 新建的子进程运行空间，采用写时复制技术, 
	else if (pid == 0) {		/* child */
		// if( mlockall(MCL_CURRENT) == -1){
		// 	err_sys("write error");	
		// }
		printf("write before globvar:");
		mem_addr_vir2phy((unsigned long)&globvar,NULL);

		globvar = globvar + 1;
		var = var + 1;

		printf("write after globvar:");
		mem_addr_vir2phy((unsigned long)&globvar,NULL);

		printf("---pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,var);

		exit(0);
	} 
	// 父进程空间
	else {
		printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,var);
		sleep(2);				/* parent */
	}

	if (waitpid(pid, NULL, 0) != pid)	/* wait for child exit*/
		err_sys("waitpid error");

	exit(0);
}


/*
	进程请以超级用户权限执行
*/
int mem_addr_vir2phy(unsigned long vir, unsigned long *phy)
{
    int fd;
    int page_size=getpagesize();
    unsigned long vir_page_idx = vir/page_size;
    unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);
    uint64_t pfn_item;
    unsigned long cal_phy;
    
    fd = open(page_map_file,O_RDONLY);
    if (fd < 0){
        printf("open %s failed", page_map_file);
        return -1;
    }
    if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET)){
        printf("lseek %s failed", page_map_file);
        return -1;
    }
    if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t))){
        printf("read %s failed", page_map_file);
        return -1;
    }
    if (0==(pfn_item & PFN_PRESENT_FLAG)){
        printf("page is not present");
        return -1;
    }

    cal_phy = (pfn_item & PFN_MASK)*page_size + vir % page_size;

    if (phy != NULL){
        *phy = cal_phy;
    }

    printf("Virtual Address:%lx  Physical Address:%lx\n",vir,cal_phy);  

    return 0;

}